<?php
/**
 * This file is part of the Anota project.
 * 2011-2012 Fernando Garcia Huerta <fgarciahue@uoc.edu>
 * 
 * UOC - FUNDAMENTOS DE INFORMÁTICA EN ENTORNOS BIOINFORMÁTICOS - PEC2
 */

/**
 * AnotaProtocol
 * 
 * Bioinformatic protocol
 * - executes geneid with user params
 * - stores, ouput, proccess time, and options in database
 * - executes geneid with application params to get a GFF file
 * - stores stats of sequences, genes and exons in database
 * - executes gff2ps to get a postscript image
 * - executes convert to get a jpg image
 * - copies jpg to a public accesible folder
 * 
 * @package    Anota
 * @subpackage application/business
 * @author     Fernando Garcia Huerta <fgarciahue@uoc.edu>
 */
class AnotaProtocol {

    protected
    $config,            // config object
    $values,            // validated values
    $command,           // real geneid executed command
    $fake_options,      // faked options to make them user 'showable'
    $fake_command,      // geneid command to be shown to de user
    $output,            // genaid output
    $gff_file,          // temp gff file
    $ps_file,           // temp ps file
    $image_file;        // image file

    /**
     * Constructor
     *  - stores in $this->command the real command line
     *  - stores in $this->fake_command the command line as it will be shown to
     *    the user.
     * 
     * @param AnotaConfig $config Application object configuration
     * @param array $values Validated form data
     */
    public function __construct($config, $values) {
        $this->config = $config;
        $this->values = $values;

        // builds geneid command
        $this->command = sprintf("%s %s %s", $this->config->getCgi(AnotaConfig::$GENEID_NAME), implode(' ', $this->values['options']), $this->values['input_file']['tmp_name']);

        // builds fake options
        $fake_options = array('param_option' => '-P ' . $_POST['param_option']);
        $this->fake_options = array_merge($this->values['options'], $fake_options);
        
        // builds fake command
        $this->fake_command = sprintf("%s %s %s", AnotaConfig::$GENEID_NAME, implode(' ', $this->fake_options), $this->values['input_file']['name']);
    }

    /**
     *  executes Bioinformatic Protocol
     */
    public function execute() {

        // inserts a proc row in database
        $proc = $this->createDboProc();

        // executes geneid with user params
        $this->output = $this->generateOutput();

        // update proc row in database 
        $this->updateDboProc($proc);

        // generate gff info for statistics and images
        $this->gff_file = $this->generateGFFdata();

        // inserts a row in database for each sequence in file
        $this->createDboSequences($proc);

        // generate images
        $this->ps_file = $this->generatePs($this->gff_file);
        $this->image_file = $this->generateImage($this->ps_file);
    }

    /**
     * Returns geneid executed command line
     * 
     * @return string
     */
    public function getCommand() {
        return $this->command;
    }

    /**
     * Returns user version of geneid executed command
     * 
     * @return string
     */
    public function getFakeCommand() {
        return $this->fake_command;
    }

    /**
     * Returns output string
     * 
     * @return string
     */
    public function getOutput() {
        return $this->output;
    }

    /**
     * Return final jpg file
     * 
     * @return string
     */
    public function getImagefile() {
        return $this->image_file;
    }

    /**
     * Executes geneid with user options and return the output 
     * 
     * @return string the output of the command line
     */
    protected function generateOutput() {
        exec($this->command, $out);
        $output = implode("\n", $out);

        // 
        // If output option where -M or -m, the ouput needs to be
        // scapped to make it html ready.
        if (isset($this->values['output_option'])) {
            if ($this->values['output_option'] == '-M' or $this->values['output_option'] == '-m') {
                $output = htmlspecialchars($output, ENT_XML1, UTF - 8);
            }
        }
        
        return $output;
    }

    /**
     * Executes geneid to obtain data in GFF format
     * 
     * @return string 
     */
    protected function generateGFFdata() {
        $geneid = $this->config->getCgi(AnotaConfig::$GENEID_NAME);
        $file = $this->values['input_file']['tmp_name'];

        $temp_file = tempnam('', 'anota_');
        $gff_file = $temp_file . '.gff';

        // build options
        $options = array();
        $options[] = '-G';
        $options[] = $this->values['options']['param_option'];
        $options[] = $file;
        $options[] = '>' . $gff_file;

        $command = sprintf("%s %s", $geneid, implode(' ', $options));
        exec($command, $out, $res);

        return $gff_file;
    }

    /**
     * Executes gff2ps to generate a ps image and returns its full path
     * 
     * @param string $gff_file
     * @return string 
     */
    function generatePs($gff_file) {
        $gff2ps = $this->config->getCgi(AnotaConfig::$GFF2PS_NAME);

        $ps_file = preg_replace('/\.gff/', '.ps', $gff_file);
        $log_file = preg_replace('/\.gff/', '.log', $gff_file);

        // build options
        $options = array();
        $options[] = '-a';
        $options[] = '<' . $gff_file;
        $options[] = '1>' . $ps_file;
        $options[] = '2>' . $log_file;

        $command = sprintf("%s %s", $gff2ps, implode(' ', $options));
        exec($command, $out, $res);

        return $ps_file;
    }

    /**
     * Converts a ps image into jpg image and stores it in an accesible path
     * 
     * @param string $ps_file
     * @return string ths full path of the generated jpg
     */
    function generateImage($ps_file) {
        $convert = $this->config->getCgi(AnotaConfig::$CONVERT_NAME);

        $image_file = preg_replace('/\.ps/', '', $ps_file) . '.jpg';

        // build options
        $options = array();
        $options[] = $ps_file;
        $options[] = '-rotate "90<"'; // rotates the image if w < h
        $options[] = $image_file;

        $command = sprintf("%s %s", $convert, implode(' ', $options));
        exec($command, $out, $res);

        // copy image to a public web folder in order to show it to de user
        $destino = $this->config->getImagesDir() . DIRECTORY_SEPARATOR . basename($image_file);
        copy($image_file, $destino);

        return basename($image_file);
    }

    /**
     * Inserts a new proc in database with user data
     * 
     * @return DboProc 
     */
    private function createDboProc() {
        $proc = new DboProc();
        $proc->ip = $this->getRealIpAddr();
        $proc->filesize = $this->values['input_file']['size'];
        $proc->options = implode(' ', $this->fake_options);
        $proc->save($this->config->getConn());
        return $proc;
    }

    /**
     * Sets de proc updated_at field in database
     * 
     * @param DboProc $proc 
     */
    private function updateDboProc(&$proc) {
        $proc->save($this->config->getConn());
    }

    /**
     * Parses gff output and inserts sequence info in database
     * 
     * @param DboProc $proc 
     */
    private function createDboSequences(&$proc) {
        // get connection object from config
        $conn = $this->config->getConn();

        // parse gff file
        $lines = file($this->gff_file);
        $seqs = array_values(preg_grep('/Sequence/', $lines));
        $genes = array_values(preg_grep('/genes\./', $lines));
        $exons = array_values(preg_grep('/exons\./', $lines));
        
        // inserts a row in database for each sequence
        for ($i = 0; $i < count($seqs); $i++) {
            // initializes sequence object
            $sequence = new DboSequence();
            $sequence->id_proc = $proc->id;

            // Length 
            $res = array();
            preg_match('/- Length = (\d+) bps/', $seqs[$i], $res);
            $sequence->length = $res[1];

            // Genes 
            $res = array();
            preg_match('/(\d+) genes/', $genes[$i], $res);
            $sequence->numgenes = $res[1];

            // Exons
            $res = array();
            preg_match('/(\d+) exons/', $exons[$i], $res);
            $sequence->numexons = $res[1];

            // and save
            $sequence->save($conn);
        }
    }

    /**
     * Get real user ip info (as nearest as possible).
     * 
     * @return type 
     */
    private function getRealIpAddr() {
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) { //check ip from share internet
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { //to check ip is pass from proxy
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } else {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        return $ip;
    }

}